package bytecode

import (
	"testing"

	"tvl.fyi/tvix/eval/internal/op"
)

func TestEmptyOp(t *testing.T) {
	var operation op.Op

	if operation != op.Invalid {
		t.Fatalf("zero opcode should be invalid sentinel value")
	}
}

func TestPushOp(t *testing.T) {
	var c Chunk
	idx := c.PushOp(op.Return)

	if c.ReadOp(idx) != op.Return {
		t.Fatalf("should read opcode that has been pushed")
	}
}

func TestPushWithArg(t *testing.T) {
	var c Chunk
	idx := c.PushOpWithUint(op.Constant, 42)

	if c.ReadOp(idx) != op.Constant {
		t.Fatalf("should read opcode that has been pushed")
	}

	arg, size := c.ReadUint(idx + 1)
	if arg != 42 {
		t.Fatalf("should read argument that has been pushed")
	}

	if size != 1 {
		t.Fatalf("expected '42' to be encoded in one byte")
	}
}

func TestPushMultipleArgs(t *testing.T) {
	var c Chunk
	idx := c.PushOpWithUintArgs(op.Constant, 42, 1337)

	if c.ReadOp(idx) != op.Constant {
		t.Fatalf("should read opcode that has been pushed")
	}

	idx++
	argCount, size := c.ReadUint(idx)
	if argCount != 2 {
		t.Fatalf("argument count should be two")
	}

	idx += size
	arg1, _ := c.ReadUint(idx)
	if arg1 != 42 {
		t.Fatalf("should read first argument that has been pushed")
	}

	idx += size
	arg2, _ := c.ReadUint(idx)
	if arg2 != 1337 {
		t.Fatalf("should read first argument that has been pushed")
	}
}

func TestPushJump(t *testing.T) {
	var c Chunk

	c.PushOpWithUint(op.Constant, 1)
	idx := c.PushJump(op.Jump)
	c.PushOpWithUint(op.Constant, 3)
	c.PatchJump(idx)

	expected := []byte{
		byte(op.Constant), 1,
		byte(op.Jump), 5, 0,
		byte(op.Constant), 3,
	}

	for i, b := range c.Code {
		if expected[i] != b {
			t.Errorf("unexpected bytecode at index %d: %v (wanted: %v)", i, b, expected[i])
		}
	}

	if t.Failed() {
		t.Errorf("failed chunk: %s", &c)
	}

	if o := c.ReadJumpOffset(idx); o != 5 {
		t.Errorf("incorrect jump offset %d (expected 5)", o)
	}
}
